package cn.gok.oa.log;
import cn.gok.oa.log.entity.Log;
import cn.gok.oa.log.mapper.LogMapper;
import cn.gok.oa.log.service.ILogService;
import cn.gok.oa.system.dto.UserDto;
import cn.gok.oa.system.entity.TbUser;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * desc: 日志切面类
 *
 * @author gaorimao
 * @since 2021-3-14
 */
@Aspect
@Component
public class LogAspect {
    private static Logger logger = LoggerFactory.getLogger(LogAspect.class);
    @Autowired
    private LogMapper logMapper;
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private SnowFlake snowflake ;

    //Service层切点
    @Pointcut("execution (* cn.gok.oa.*.service.impl..*.*(..))")
    public  void serviceAspect() {
    }

    /**
     * 异常通知 用于拦截service层记录异常日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
    public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Log log = new Log();
        TbUser user = (TbUser)request.getSession().getAttribute("LOGIN_USER");
        log.setAction("service层拦截到异常");
        log.setUserId(user.getId());
        log.setRequestIp(request.getRemoteHost());
        log.setRequestUrl(request.getRequestURI());
        log.setRequestType(request.getMethod());
        log.setErrorMessage(e.toString());
        Method method = getMethod(joinPoint);
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        log.setRequestMethod(classMethod);
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            try {
                params = handleParams(params, args, Arrays.asList(paramNames));
            } catch (JsonProcessingException ee) {
                logger.error("[LogAspect] setSysLogInfo handleParams error = {}",ee);
            }
            log.setRequestParams(params.toString());
        }
        logMapper.insert(log);
    }

    @Pointcut("@annotation(cn.gok.oa.log.LogAnnotation)")
    public void pointcut() {}

    /**
     * desc: 前置通知，记录controller层操作
     *
     * @param joinPoint 切入点
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        try {
            handleLog(joinPoint);
        } catch (Exception e) {
            logger.error("[LogAspect] doBefore error = {}",e);
        }
    }

    /**
     * desc: 通过反射获取日志信息，存入数据库
     *
     * @param joinPoint 切点
     * @throws Exception
     */
    private void handleLog(final JoinPoint joinPoint) throws Exception{
        // 获得注解
        Method method = getMethod(joinPoint);
        LogAnnotation logAnnotation = getAnnotationLog(method);
        if (logAnnotation == null) {
            return;
        }
        Log log = setLogInfo(joinPoint,method,logAnnotation);
        logger.info("[log] log info : {}", JSON.toJSONString(log));
        logMapper.insert(log);
    }

    /**
     * desc: 初始化日志信息，存入数据库
     *
     * @return SysLog
     */
    private Log setLogInfo(JoinPoint joinPoint, Method method, LogAnnotation logAnnotation) {
        // 操作数据库日志表
        Log log = new Log();
        log.setId(snowflake.nextId());
        log.setAction(logAnnotation.value());
        //触发人
        TbUser user = (TbUser)request.getSession().getAttribute("LOGIN_USER");
        logger.info("[log] loginUserId : {}", user.getId());
        log.setUserId(user.getId());
        log.setRequestIp(request.getRemoteHost());
        log.setRequestUrl(request.getRequestURI());
        log.setRequestType(request.getMethod());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        log.setRequestMethod(classMethod);
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            try {
                params = handleParams(params, args, Arrays.asList(paramNames));
            } catch (JsonProcessingException e) {
                logger.error("[LogAspect] setSysLogInfo handleParams error = {}",e);
            }
            log.setRequestParams(params.toString());
        }
        return log;
    }

    /**
     *dsec: 是否存在注解，如果存在就获取
     */
    private LogAnnotation getAnnotationLog(Method method) {
        if (method != null) {
            return method.getAnnotation(LogAnnotation.class);
        }
        return null;
    }

    /**
     * desc: 通过反射或缺当前请求方法
     *
     * @param joinPoint 切点
     * @return Method
     */
    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method;
        }
        return null;
    }

    /**
     * desc: 处理请求参数
     *
     * @param params
     * @param args
     * @param paramNames
     * @return
     * @throws JsonProcessingException
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map) args[i]).keySet();
                List list = new ArrayList();
                List paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map) args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[]{null});
                        // 如果不抛出NoSuchMethodException 异常则存在 toString 方法 ，安全的writeValueAsString ，否则 走 Object的 toString方法
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) args[i];
                    params.append("  ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append("  ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }
}